<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>（Vue源码七）Vue 事件 | Jeffery Blog</title><meta name="keywords" content="vue, 组件化，vue 源码"><meta name="author" content="志非"><meta name="copyright" content="志非"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="vue, vue 源码，Vue 组件化">
<meta property="og:type" content="article">
<meta property="og:title" content="（Vue源码七）Vue 事件">
<meta property="og:url" content="https://fif.gitee.io/blog/2021/05/14/vue/vue-code-9.event/index.html">
<meta property="og:site_name" content="Jeffery Blog">
<meta property="og:description" content="vue, vue 源码，Vue 组件化">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png">
<meta property="article:published_time" content="2021-05-13T16:00:00.000Z">
<meta property="article:modified_time" content="2021-05-13T16:00:00.000Z">
<meta property="article:author" content="志非">
<meta property="article:tag" content="vue">
<meta property="article:tag" content="vue 源码">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png"><link rel="shortcut icon" href="/blog/img/home/avatar.jpeg"><link rel="canonical" href="https://fif.gitee.io/blog/2021/05/14/vue/vue-code-9.event/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//fonts.googleapis.com" crossorigin=""/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/blog/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.js@2.10.5/dist/instantsearch.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/instantsearch.js@2.10.5/dist/instantsearch.min.js" defer></script><script async="async" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><script>(adsbygoogle = window.adsbygoogle || []).push({
  google_ad_client: '',
  enable_page_level_ads: 'true'
});</script><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Titillium+Web&amp;display=swap" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/blog/',
  algolia: {"appId":"QYKLQ4BSGP","apiKey":"d45fc66694b4260b20711a30fdb5c4e1","indexName":"jeffery-blog","hits":{"per_page":6},"languages":{"input_placeholder":"搜索文章","hits_empty":"找不到您查询的内容：${query}","hits_stats":"找到 ${hits} 条结果，用时 ${time} 毫秒"}},
  localSearch: undefined,
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
  noticeOutdate: {"limitDay":200,"position":"top","messagePrev":"本文修改于","messageNext":"天前，如果本文有一定的时效性，那么本文可能已经失效~"},
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: true,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'mediumZoom',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#121212","position":"bottom-left"},
  source: {
    jQuery: 'https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js',
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/js/jquery.justifiedGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/css/justifiedGallery.min.css'
    },
    fancybox: {
      js: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js',
      css: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: true,
  isanchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = { 
  isPost: true,
  isHome: false,
  isHighlightShrink: true,
  isToc: true,
  postUpdate: '2021-05-14 00:00:00'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    })(window)</script><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/blog/atom.xml" title="Jeffery Blog" type="application/atom+xml">
</head><body><div id="loading-box"><div class="loading-left-bg"></div><div class="loading-right-bg"></div><img class="load-image" src= "" data-lazy-src="/blog/img/loading.gif" alt=""/></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="author-avatar"><img class="avatar-img" src= "" data-lazy-src="/blog/img/home/avatar.jpeg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data"><div class="data-item is-center"><div class="data-item-link"><a href="/blog/archives/"><div class="headline">文章</div><div class="length-num">12</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/blog/tags/"><div class="headline">标签</div><div class="length-num">6</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/blog/categories/"><div class="headline">分类</div><div class="length-num">7</div></a></div></div></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/blog/"><i class="fa-fw fa fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/blog/archives/"><i class="fa-fw fa fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/blog/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/blog/categories/"><i class="fa-fw fa fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 媒体</span><i class="fas fa-chevron-down expand"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/blog/books/"><i class="fa-fw fas fa-book"></i><span> 阅读</span></a></li><li><a class="site-page child" href="/blog/music/"><i class="fa-fw fa fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/blog/movies/"><i class="fa-fw fa fa-film"></i><span> 电影</span></a></li><li><a class="site-page child" href="/blog/gallery/"><i class="fa-fw far fa-image"></i><span> 相册</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/blog/box/"><i class="fa-fw fab fa-xbox"></i><span> 聚宝盒</span></a></div><div class="menus_item"><a class="site-page" href="/blog/talk/"><i class="fa-fw far fa-comment"></i><span> 微语</span></a></div><div class="menus_item"><a class="site-page" href="/blog/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/blog/about/"><i class="fa-fw fa fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/blog/">Jeffery Blog</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/blog/"><i class="fa-fw fa fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/blog/archives/"><i class="fa-fw fa fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/blog/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/blog/categories/"><i class="fa-fw fa fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 媒体</span><i class="fas fa-chevron-down expand"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/blog/books/"><i class="fa-fw fas fa-book"></i><span> 阅读</span></a></li><li><a class="site-page child" href="/blog/music/"><i class="fa-fw fa fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/blog/movies/"><i class="fa-fw fa fa-film"></i><span> 电影</span></a></li><li><a class="site-page child" href="/blog/gallery/"><i class="fa-fw far fa-image"></i><span> 相册</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/blog/box/"><i class="fa-fw fab fa-xbox"></i><span> 聚宝盒</span></a></div><div class="menus_item"><a class="site-page" href="/blog/talk/"><i class="fa-fw far fa-comment"></i><span> 微语</span></a></div><div class="menus_item"><a class="site-page" href="/blog/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/blog/about/"><i class="fa-fw fa fa-heart"></i><span> 关于</span></a></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">（Vue源码七）Vue 事件</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2021-05-13T16:00:00.000Z" title="发表于 2021-05-14 00:00:00">2021-05-14</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2021-05-13T16:00:00.000Z" title="更新于 2021-05-14 00:00:00">2021-05-14</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/%E5%89%8D%E7%AB%AF/">前端</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/%E5%89%8D%E7%AB%AF/vue/">vue</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/%E5%89%8D%E7%AB%AF/vue/vue-%E6%BA%90%E7%A0%81/">vue 源码</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">5.4k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>24分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="（Vue源码七）Vue 事件"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>在日常开发中，可以在元素中绑定原生事件处理交互，例如 <code>@click=***</code>，也可以在组件中利用自定义事件实现父子间的通信，那原生 <code>dom</code> 事件和自定义事件有什么不同的地方吗，实现通信的原理又是什么，带着问题，来研究分析事件原理。</p>
<h2 id="模板编译"><a href="#模板编译" class="headerlink" title="模板编译"></a>模板编译</h2><p>我们使用<code>v-on</code>或者<code>@</code>在模板上绑定事件，因此对事件的第一步处理，就是在编译阶段对事件指令做处理。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">v-on:click.stop</span>=<span class="string">&quot;doThis&quot;</span>&gt;</span>点击<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;&#123;count&#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript"><span class="keyword">var</span> vm = <span class="keyword">new</span> Vue(&#123;</span></span><br><span class="line"><span class="javascript">    el: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="javascript">    <span class="function"><span class="title">data</span>(<span class="params"></span>)</span> &#123;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">return</span> &#123;</span></span><br><span class="line">            count: 1</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    methods: &#123;</span><br><span class="line"><span class="javascript">        <span class="function"><span class="title">doThis</span>(<span class="params"></span>)</span> &#123;</span></span><br><span class="line"><span class="javascript">            ++<span class="built_in">this</span>.count</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>在模板编译的入口<code>src\compiler\parser\index.js</code>，<code>var ast = parse(template.trim(), options);</code>中，<code>parse</code>通过正则匹配，将其解析为一个<code>AST</code>树，其中对于属性的处理，文件路径的<code>processAttr</code>中</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> dirRE = <span class="regexp">/^v-|^@|^:/</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processAttrs</span> (<span class="params">el</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> list = el.attrsList;</span><br><span class="line">    <span class="keyword">var</span> i, l, name, rawName, value, modifiers, syncGen, isDynamic;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>, l = list.length; i &lt; l; i++) &#123;</span><br><span class="line">      name = rawName = list[i].name; <span class="comment">// v-on:click</span></span><br><span class="line">      value = list[i].value; <span class="comment">// doThis</span></span><br><span class="line">      <span class="keyword">if</span> (dirRE.test(name)) &#123; <span class="comment">// 匹配v-或者@开头的指令</span></span><br><span class="line">        el.hasBindings = <span class="literal">true</span>;</span><br><span class="line">        modifiers = parseModifiers(name.replace(dirRE, <span class="string">&#x27;&#x27;</span>));<span class="comment">// parseModifiers(&#x27;on:click&#x27;)</span></span><br><span class="line">        <span class="keyword">if</span> (modifiers) &#123;</span><br><span class="line">          name = name.replace(modifierRE, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (bindRE.test(name)) &#123; <span class="comment">// v-bind分支</span></span><br><span class="line">          <span class="comment">// ...留到v-bind指令时分析</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (onRE.test(name)) &#123; <span class="comment">// v-on分支</span></span><br><span class="line">          name = name.replace(onRE, <span class="string">&#x27;&#x27;</span>); <span class="comment">// 拿到真正的事件click</span></span><br><span class="line">          isDynamic = dynamicArgRE.test(name);<span class="comment">// 动态事件绑定</span></span><br><span class="line">          <span class="keyword">if</span> (isDynamic) &#123;</span><br><span class="line">            name = name.slice(<span class="number">1</span>, -<span class="number">1</span>);</span><br><span class="line">          &#125;</span><br><span class="line">          addHandler(el, name, value, modifiers, <span class="literal">false</span>, warn$<span class="number">2</span>, list[i], isDynamic);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123; <span class="comment">// normal directives</span></span><br><span class="line">         <span class="comment">// 其他指令相关逻辑</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p><code>var dirRE = /^v-|^@|^:/;</code> 匹配事件和绑定的正则，命中匹配再通过 <code>onRE.test(name)</code> 匹配事件指令相关内容，包括事件本身，事件回调以及事件修饰符。最终通过<code>addHandler</code>方法，为<code>AST</code>树添加事件相关的属性。而<code>addHandler</code>还有一个重要功能是对事件修饰符进行特殊处理。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// el是当前解析的AST树</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">addHandler</span> (<span class="params">el,name,value,modifiers,important,warn,range,dynamic</span>) </span>&#123;</span><br><span class="line">    modifiers = modifiers || emptyObject;</span><br><span class="line">    <span class="comment">// passive 和 prevent不能同时使用，可以参照官方文档说明</span></span><br><span class="line">    <span class="keyword">if</span> (</span><br><span class="line">      warn &amp;&amp;</span><br><span class="line">      modifiers.prevent &amp;&amp; modifiers.passive</span><br><span class="line">    ) &#123;</span><br><span class="line">      warn(</span><br><span class="line">        <span class="string">&#x27;passive and prevent can\&#x27;t be used together. &#x27;</span> +</span><br><span class="line">        <span class="string">&#x27;Passive handler can\&#x27;t prevent default event.&#x27;</span>,</span><br><span class="line">        range</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 这部分的逻辑会对特殊的修饰符做字符串拼接的处理，以备后续的使用</span></span><br><span class="line">    <span class="keyword">if</span> (modifiers.right) &#123;</span><br><span class="line">      <span class="keyword">if</span> (dynamic) &#123;</span><br><span class="line">        name = <span class="string">&quot;(&quot;</span> + name + <span class="string">&quot;)===&#x27;click&#x27;?&#x27;contextmenu&#x27;:(&quot;</span> + name + <span class="string">&quot;)&quot;</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (name === <span class="string">&#x27;click&#x27;</span>) &#123;</span><br><span class="line">        name = <span class="string">&#x27;contextmenu&#x27;</span>;</span><br><span class="line">        <span class="keyword">delete</span> modifiers.right;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (modifiers.middle) &#123;</span><br><span class="line">      <span class="keyword">if</span> (dynamic) &#123;</span><br><span class="line">        name = <span class="string">&quot;(&quot;</span> + name + <span class="string">&quot;)===&#x27;click&#x27;?&#x27;mouseup&#x27;:(&quot;</span> + name + <span class="string">&quot;)&quot;</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (name === <span class="string">&#x27;click&#x27;</span>) &#123;</span><br><span class="line">        name = <span class="string">&#x27;mouseup&#x27;</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (modifiers.capture) &#123;</span><br><span class="line">      <span class="keyword">delete</span> modifiers.capture;</span><br><span class="line">      name = prependModifierMarker(<span class="string">&#x27;!&#x27;</span>, name, dynamic);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (modifiers.once) &#123;</span><br><span class="line">      <span class="keyword">delete</span> modifiers.once;</span><br><span class="line">      name = prependModifierMarker(<span class="string">&#x27;~&#x27;</span>, name, dynamic);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">    <span class="keyword">if</span> (modifiers.passive) &#123;</span><br><span class="line">      <span class="keyword">delete</span> modifiers.passive;</span><br><span class="line">      name = prependModifierMarker(<span class="string">&#x27;&amp;&#x27;</span>, name, dynamic);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// events 用来记录绑定的事件</span></span><br><span class="line">    <span class="keyword">var</span> events;</span><br><span class="line">    <span class="keyword">if</span> (modifiers.native) &#123;</span><br><span class="line">      <span class="keyword">delete</span> modifiers.native;</span><br><span class="line">      events = el.nativeEvents || (el.nativeEvents = &#123;&#125;);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      events = el.events || (el.events = &#123;&#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> newHandler = rangeSetItem(&#123; <span class="attr">value</span>: value.trim(), <span class="attr">dynamic</span>: dynamic &#125;, range);</span><br><span class="line">    <span class="keyword">if</span> (modifiers !== emptyObject) &#123;</span><br><span class="line">      newHandler.modifiers = modifiers;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> handlers = events[name];</span><br><span class="line">    <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">    <span class="comment">// 绑定的事件可以多个，回调也可以多个，最终会合并到数组中</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(handlers)) &#123;</span><br><span class="line">      important ? handlers.unshift(newHandler) : handlers.push(newHandler);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (handlers) &#123;</span><br><span class="line">      events[name] = important ? [newHandler, handlers] : [handlers, newHandler];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      events[name] = newHandler;</span><br><span class="line">    &#125;</span><br><span class="line">    el.plain = <span class="literal">false</span>;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>修饰符的处理会改变最终字符串的拼接结果，我们看最终转换的<code>AST</code>树：</p>
<p><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/9.1.png" alt="9.1. 模板编译 - 图1"></p>
<h2 id="代码生成"><a href="#代码生成" class="headerlink" title="代码生成"></a>代码生成</h2><p>模板编译的最后一步是根据解析完的<code>AST</code>树生成对应平台的渲染函数，也就是<code>render</code>函数的生成过程, 对应<code>var code = generate(ast, options);</code>。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">generate</span> (<span class="params">ast,options</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> state = <span class="keyword">new</span> CodegenState(options);</span><br><span class="line">    <span class="keyword">var</span> code = ast ? genElement(ast, state) : <span class="string">&#x27;_c(&quot;div&quot;)&#x27;</span>;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      render: (<span class="string">&quot;with(this)&#123;return &quot;</span> + code + <span class="string">&quot;&#125;&quot;</span>), <span class="comment">// with函数</span></span><br><span class="line">      staticRenderFns: state.staticRenderFns</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>其中核心处理在<code>getElement</code>中,<code>getElement</code>函数会根据不同指令类型处理不同的分支，对于普通模板的编译会进入<code>genData</code>函数中处理</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">genElement</span> (<span class="params">el: ASTElement, state: CodegenState</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (el.parent) &#123;</span><br><span class="line">    el.pre = el.pre || el.parent.pre</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (el.staticRoot &amp;&amp; !el.staticProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genStatic(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.once &amp;&amp; !el.onceProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genOnce(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.for &amp;&amp; !el.forProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genFor(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.if &amp;&amp; !el.ifProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genIf(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.tag === <span class="string">&#x27;template&#x27;</span> &amp;&amp; !el.slotTarget &amp;&amp; !state.pre) &#123;</span><br><span class="line">    <span class="keyword">return</span> genChildren(el, state) || <span class="string">&#x27;void 0&#x27;</span></span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.tag === <span class="string">&#x27;slot&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> genSlot(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// component or element</span></span><br><span class="line">    <span class="keyword">let</span> code</span><br><span class="line">    <span class="keyword">if</span> (el.component) &#123;</span><br><span class="line">      code = genComponent(el.component, el, state)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">let</span> data</span><br><span class="line">      <span class="keyword">if</span> (!el.plain || (el.pre &amp;&amp; state.maybeComponent(el))) &#123;</span><br><span class="line">        data = genData(el, state)</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> children = el.inlineTemplate ? <span class="literal">null</span> : genChildren(el, state, <span class="literal">true</span>)</span><br><span class="line">      code = <span class="string">`_c(&#x27;<span class="subst">$&#123;el.tag&#125;</span>&#x27;<span class="subst">$&#123;</span></span></span><br><span class="line"><span class="string"><span class="subst">        data ? <span class="string">`,<span class="subst">$&#123;data&#125;</span>`</span> : <span class="string">&#x27;&#x27;</span> <span class="regexp">//</span> data</span></span></span><br><span class="line"><span class="string"><span class="subst">      &#125;</span><span class="subst">$&#123;</span></span></span><br><span class="line"><span class="string"><span class="subst">        children ? <span class="string">`,<span class="subst">$&#123;children&#125;</span>`</span> : <span class="string">&#x27;&#x27;</span> <span class="regexp">//</span> children</span></span></span><br><span class="line"><span class="string"><span class="subst">      &#125;</span>)`</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// module transforms</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; state.transforms.length; i++) &#123;</span><br><span class="line">      code = state.transforms[i](el, code)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> code</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从前面解析出的<code>AST</code>树明显看出，<code>AST</code>树中多了<code>events</code>的属性,<code>genHandlers</code>函数会为<code>event</code>属性做逻辑处理。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">genData</span> (<span class="params">el, state</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> data = <span class="string">&#x27;&#123;&#x27;</span>;</span><br><span class="line">    <span class="comment">// directives first.</span></span><br><span class="line">    <span class="comment">// directives may mutate the el&#x27;s other properties before they are generated.</span></span><br><span class="line">    <span class="keyword">var</span> dirs = genDirectives(el, state);</span><br><span class="line">    <span class="keyword">if</span> (dirs) &#123; data += dirs + <span class="string">&#x27;,&#x27;</span>; &#125;</span><br><span class="line">    <span class="comment">//其他处理</span></span><br><span class="line">    ···</span><br><span class="line">    <span class="comment">// event handlers</span></span><br><span class="line">    <span class="keyword">if</span> (el.events) &#123;</span><br><span class="line">      data += (genHandlers(el.events, <span class="literal">false</span>)) + <span class="string">&quot;,&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 原生事件</span></span><br><span class="line">    <span class="keyword">if</span> (el.nativeEvents) &#123;</span><br><span class="line">      data += <span class="string">`<span class="subst">$&#123;genHandlers(el.nativeEvents, <span class="literal">true</span>)&#125;</span>,`</span></span><br><span class="line">     &#125;</span><br><span class="line">    ···</span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p><code>genHandlers</code>的逻辑，会遍历解析好的<code>AST</code>树，拿到<code>event</code>对象属性，并根据属性上的事件对象拼接成字符串。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">genHandlers</span> (<span class="params">events,isNative</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> prefix = isNative ? <span class="string">&#x27;nativeOn:&#x27;</span> : <span class="string">&#x27;on:&#x27;</span>;</span><br><span class="line">    <span class="keyword">var</span> staticHandlers = <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="keyword">var</span> dynamicHandlers = <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="comment">// 遍历ast树解析好的event对象</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> name <span class="keyword">in</span> events) &#123;</span><br><span class="line">      <span class="comment">//genHandler本质上是将事件对象转换成可拼接的字符串</span></span><br><span class="line">      <span class="keyword">var</span> handlerCode = genHandler(events[name]);</span><br><span class="line">      <span class="keyword">if</span> (events[name] &amp;&amp; events[name].dynamic) &#123;</span><br><span class="line">        dynamicHandlers += name + <span class="string">&quot;,&quot;</span> + handlerCode + <span class="string">&quot;,&quot;</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        staticHandlers += <span class="string">&quot;\&quot;&quot;</span> + name + <span class="string">&quot;\&quot;:&quot;</span> + handlerCode + <span class="string">&quot;,&quot;</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    staticHandlers = <span class="string">&quot;&#123;&quot;</span> + (staticHandlers.slice(<span class="number">0</span>, -<span class="number">1</span>)) + <span class="string">&quot;&#125;&quot;</span>;</span><br><span class="line">    <span class="keyword">if</span> (dynamicHandlers) &#123;</span><br><span class="line">      <span class="keyword">return</span> prefix + <span class="string">&quot;_d(&quot;</span> + staticHandlers + <span class="string">&quot;,[&quot;</span> + (dynamicHandlers.slice(<span class="number">0</span>, -<span class="number">1</span>)) + <span class="string">&quot;])&quot;</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> prefix + staticHandlers</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"><span class="comment">// 事件模板书写匹配</span></span><br><span class="line"><span class="keyword">var</span> isMethodPath = simplePathRE.test(handler.value); <span class="comment">// doThis</span></span><br><span class="line"><span class="keyword">var</span> isFunctionExpression = fnExpRE.test(handler.value); <span class="comment">// () =&gt; &#123;&#125; or function() &#123;&#125;</span></span><br><span class="line"><span class="keyword">var</span> isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, <span class="string">&#x27;&#x27;</span>)); <span class="comment">// doThis($event)</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">genHandler</span> (<span class="params">handler</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!handler) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&#x27;function()&#123;&#125;&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 事件绑定可以多个，多个在解析ast树时会以数组的形式存在，如果有多个则会递归调用getHandler方法返回数组。</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(handler)) &#123;</span><br><span class="line">      <span class="keyword">return</span> (<span class="string">&quot;[&quot;</span> + (handler.map(<span class="function"><span class="keyword">function</span> (<span class="params">handler</span>) </span>&#123; <span class="keyword">return</span> genHandler(handler); &#125;).join(<span class="string">&#x27;,&#x27;</span>)) + <span class="string">&quot;]&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// value： doThis 可以有三种方式</span></span><br><span class="line">    <span class="keyword">var</span> isMethodPath = simplePathRE.test(handler.value); <span class="comment">// doThis</span></span><br><span class="line">    <span class="keyword">var</span> isFunctionExpression = fnExpRE.test(handler.value); <span class="comment">// () =&gt; &#123;&#125; or function() &#123;&#125;</span></span><br><span class="line">    <span class="keyword">var</span> isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, <span class="string">&#x27;&#x27;</span>)); <span class="comment">// doThis($event)</span></span><br><span class="line">    <span class="comment">// 没有任何修饰符</span></span><br><span class="line">    <span class="keyword">if</span> (!handler.modifiers) &#123;</span><br><span class="line">      <span class="comment">// 符合函数定义规范，则直接返回调用函数名 doThis</span></span><br><span class="line">      <span class="keyword">if</span> (isMethodPath || isFunctionExpression) &#123;</span><br><span class="line">        <span class="keyword">return</span> handler.value</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 不符合则通过function函数封装返回</span></span><br><span class="line">      <span class="keyword">return</span> (<span class="string">&quot;function($event)&#123;&quot;</span> + (isFunctionInvocation ? (<span class="string">&quot;return &quot;</span> + (handler.value)) : handler.value) + <span class="string">&quot;&#125;&quot;</span>) <span class="comment">// inline statement</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 包含修饰符的场景</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>模板中事件的写法有三种,分别对应上诉上个正则匹配的内容。</p>
<ol>
<li><code>&lt;div @click=&quot;doThis&quot;&gt;&lt;/div&gt;</code></li>
<li><code>&lt;div @click=&quot;doThis($event)&quot;&gt;&lt;/div&gt;</code></li>
<li><code>&lt;div @click=&quot;()=&gt;&#123;&#125;&quot;&gt;&lt;/div&gt; &lt;div @click=&quot;function()&#123;&#125;&quot;&gt;&lt;/div&gt;</code></li>
</ol>
<p>上述对事件对象的转换，如果事件不带任何修饰符，并且满足正确的模板写法，则直接返回调用事件名，如果不满足，则有可能是<code>&lt;div @click=&quot;console.log(11)&quot;&gt;&lt;/div&gt;</code>的写法，此时会封装到<code>function($event)&#123;&#125;</code>中。</p>
<p>包含修饰符的场景较多，我们单独列出分析。以上文中的例子说明，<code>modifiers: &#123; stop: true &#125;</code>会拿到<code>stop</code>对应需要添加的逻辑脚本<code>&#39;$event.stopPropagation();&#39;</code>,并将它添加到函数字符串中返回。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">genHandler</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="comment">// ···</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> code = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">    <span class="keyword">var</span> genModifierCode = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">    <span class="keyword">var</span> keys = [];</span><br><span class="line">    <span class="comment">// 遍历modifiers上记录的修饰符</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> handler.modifiers) &#123;</span><br><span class="line">      <span class="keyword">if</span> (modifierCode[key]) &#123;</span><br><span class="line">        <span class="comment">// 根据修饰符添加对应js的代码</span></span><br><span class="line">        genModifierCode += modifierCode[key];</span><br><span class="line">        <span class="comment">// left/right</span></span><br><span class="line">        <span class="keyword">if</span> (keyCodes[key]) &#123;</span><br><span class="line">          keys.push(key);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 针对exact的处理</span></span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key === <span class="string">&#x27;exact&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">var</span> modifiers = (handler.modifiers);</span><br><span class="line">        genModifierCode += genGuard(</span><br><span class="line">          [<span class="string">&#x27;ctrl&#x27;</span>, <span class="string">&#x27;shift&#x27;</span>, <span class="string">&#x27;alt&#x27;</span>, <span class="string">&#x27;meta&#x27;</span>]</span><br><span class="line">            .filter(<span class="function"><span class="keyword">function</span> (<span class="params">keyModifier</span>) </span>&#123; <span class="keyword">return</span> !modifiers[keyModifier]; &#125;)</span><br><span class="line">            .map(<span class="function"><span class="keyword">function</span> (<span class="params">keyModifier</span>) </span>&#123; <span class="keyword">return</span> (<span class="string">&quot;$event.&quot;</span> + keyModifier + <span class="string">&quot;Key&quot;</span>); &#125;)</span><br><span class="line">            .join(<span class="string">&#x27;||&#x27;</span>)</span><br><span class="line">        );</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        keys.push(key);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (keys.length) &#123;</span><br><span class="line">      code += genKeyFilter(keys);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// Make sure modifiers like prevent and stop get executed after key filtering</span></span><br><span class="line">    <span class="keyword">if</span> (genModifierCode) &#123;</span><br><span class="line">      code += genModifierCode;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 根据三种不同的书写模板返回不同的字符串</span></span><br><span class="line">    <span class="keyword">var</span> handlerCode = isMethodPath</span><br><span class="line">      ? (<span class="string">&quot;return &quot;</span> + (handler.value) + <span class="string">&quot;($event)&quot;</span>)</span><br><span class="line">      : isFunctionExpression</span><br><span class="line">        ? (<span class="string">&quot;return (&quot;</span> + (handler.value) + <span class="string">&quot;)($event)&quot;</span>)</span><br><span class="line">        : isFunctionInvocation</span><br><span class="line">          ? (<span class="string">&quot;return &quot;</span> + (handler.value))</span><br><span class="line">          : handler.value;</span><br><span class="line">    <span class="keyword">return</span> (<span class="string">&quot;function($event)&#123;&quot;</span> + code + handlerCode + <span class="string">&quot;&#125;&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> modifierCode = &#123;</span><br><span class="line">  stop: <span class="string">&#x27;$event.stopPropagation();&#x27;</span>,</span><br><span class="line">  prevent: <span class="string">&#x27;$event.preventDefault();&#x27;</span>,</span><br><span class="line">  self: genGuard(<span class="string">&quot;$event.target !== $event.currentTarget&quot;</span>),</span><br><span class="line">  ctrl: genGuard(<span class="string">&quot;!$event.ctrlKey&quot;</span>),</span><br><span class="line">  shift: genGuard(<span class="string">&quot;!$event.shiftKey&quot;</span>),</span><br><span class="line">  alt: genGuard(<span class="string">&quot;!$event.altKey&quot;</span>),</span><br><span class="line">  meta: genGuard(<span class="string">&quot;!$event.metaKey&quot;</span>),</span><br><span class="line">  left: genGuard(<span class="string">&quot;&#x27;button&#x27; in $event &amp;&amp; $event.button !== 0&quot;</span>),</span><br><span class="line">  middle: genGuard(<span class="string">&quot;&#x27;button&#x27; in $event &amp;&amp; $event.button !== 1&quot;</span>),</span><br><span class="line">  right: genGuard(<span class="string">&quot;&#x27;button&#x27; in $event &amp;&amp; $event.button !== 2&quot;</span>)</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>经过这一转换后，生成<code>with</code>封装的<code>render</code>函数如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">_c(<span class="string">&#x27;div&#x27;</span>, &#123;</span><br><span class="line">    attrs: &#123;</span><br><span class="line">        <span class="string">&quot;id &quot;</span>: <span class="string">&quot;app &quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;, [_c(<span class="string">&#x27;div&#x27;</span>, &#123;</span><br><span class="line">    on: &#123;</span><br><span class="line">        <span class="string">&quot;</span></span><br><span class="line"><span class="string">click &quot;</span>: <span class="function"><span class="keyword">function</span>(<span class="params">$event</span>) </span>&#123;</span><br><span class="line">            $event.stopPropagation();</span><br><span class="line">            <span class="keyword">return</span> doThis($event)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, [_v(<span class="string">&quot;点击 &quot;</span>)]), _v(<span class="string">&quot;&quot;</span>), _c(<span class="string">&#x27;span&#x27;</span>, [_v(_s(count))])])</span><br></pre></td></tr></table></figure>



<h2 id="事件绑定"><a href="#事件绑定" class="headerlink" title="事件绑定"></a>事件绑定</h2><p>经过编译完成后得到 <code>render</code> 函数，<code>render</code> 函数执行生成实例挂载需要的 <code>Vnode</code> 树，并且进行<code>patchVnode</code>的过程生成真实 <code>DOM</code> 节点，这个过程会绑定注册事件。</p>
<p>首先看一下绑定事件生成 <code>Vnode</code> 的结构</p>
<p><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/9.2.png" alt="9.3. 事件绑定 - 图1"></p>
<p>有了<code>Vnode</code>,接下来会遍历子节点递归调用<code>createElm</code>为每个子节点创建真实的<code>DOM</code>,由于<code>Vnode</code>中有<code>data</code>属性，在创建真实<code>DOM</code>时会进行注册相关钩子的过程，其中一个就是注册事件相关处理。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createElm</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  ···</span><br><span class="line">  <span class="comment">// 针对指令的处理</span></span><br><span class="line">   <span class="keyword">if</span> (isDef(data)) &#123;</span><br><span class="line">      invokeCreateHooks(vnode, insertedVnodeQueue);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">invokeCreateHooks</span> (<span class="params">vnode, insertedVnodeQueue</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> i$<span class="number">1</span> = <span class="number">0</span>; i$<span class="number">1</span> &lt; cbs.create.length; ++i$<span class="number">1</span>) &#123;</span><br><span class="line">    cbs.create[i$<span class="number">1</span>](emptyNode, vnode);</span><br><span class="line">  &#125;</span><br><span class="line">  i = vnode.data.hook; <span class="comment">// Reuse variable</span></span><br><span class="line">  <span class="keyword">if</span> (isDef(i)) &#123;</span><br><span class="line">    <span class="keyword">if</span> (isDef(i.create)) &#123; i.create(emptyNode, vnode); &#125;</span><br><span class="line">    <span class="keyword">if</span> (isDef(i.insert)) &#123; insertedVnodeQueue.push(vnode); &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> events = &#123;</span><br><span class="line">  create: updateDOMListeners,</span><br><span class="line">  update: updateDOMListeners</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p><code>updateDOMListeners</code>对真实的<code>DOM</code>节点注册事件任务。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">updateDOMListeners</span> (<span class="params">oldVnode, vnode</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// on是事件指令的标志</span></span><br><span class="line">  <span class="keyword">if</span> (isUndef(oldVnode.data.on) &amp;&amp; isUndef(vnode.data.on)) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 新旧节点不同的事件绑定解绑</span></span><br><span class="line">  <span class="keyword">var</span> on = vnode.data.on || &#123;&#125;;</span><br><span class="line">  <span class="keyword">var</span> oldOn = oldVnode.data.on || &#123;&#125;;</span><br><span class="line">  <span class="comment">// 拿到需要添加事件的真实DOM节点</span></span><br><span class="line">  target$<span class="number">1</span> = vnode.elm;</span><br><span class="line">  <span class="comment">// normalizeEvents是对事件兼容性的处理</span></span><br><span class="line">  normalizeEvents(on);</span><br><span class="line">  updateListeners(on, oldOn, add$<span class="number">1</span>, remove$<span class="number">2</span>, createOnceHandler$<span class="number">1</span>, vnode.context);</span><br><span class="line">  target$<span class="number">1</span> = <span class="literal">undefined</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中<code>normalizeEvents</code>是针对<code>v-model</code>的处理,例如在IE下不支持<code>change</code>事件，只能用<code>input</code>事件代替。</p>
<p><code>updateListeners</code>的逻辑会遍历<code>on</code>事件对新节点事件绑定注册事件，对旧节点移除事件监听，它即要处理原生<code>DOM</code>事件的添加和移除，也要处理自定义事件的添加和移除，关于自定义事件，后续内容再分析。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">updateListeners</span> (<span class="params">on,oldOn,add,remove###<span class="number">1</span>,createOnceHandler,vm</span>) </span>&#123;</span><br><span class="line">    var name, def###1, cur, old, event;</span><br><span class="line">    <span class="comment">// 遍历事件</span></span><br><span class="line">    <span class="keyword">for</span> (name <span class="keyword">in</span> on) &#123;</span><br><span class="line">      def###1 = cur = on[name];</span><br><span class="line">      old = oldOn[name];</span><br><span class="line">      event = normalizeEvent(name);</span><br><span class="line">      <span class="keyword">if</span> (isUndef(cur)) &#123;</span><br><span class="line">        <span class="comment">// 事件名非法的报错处理</span></span><br><span class="line">        warn(</span><br><span class="line">          <span class="string">&quot;Invalid handler for event \&quot;&quot;</span> + (event.name) + <span class="string">&quot;\&quot;: got &quot;</span> + <span class="built_in">String</span>(cur),</span><br><span class="line">          vm</span><br><span class="line">        );</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isUndef(old)) &#123;</span><br><span class="line">        <span class="comment">// 旧节点不存在</span></span><br><span class="line">        <span class="keyword">if</span> (isUndef(cur.fns)) &#123;</span><br><span class="line">          <span class="comment">// createFunInvoker返回事件最终执行的回调函数</span></span><br><span class="line">          cur = on[name] = createFnInvoker(cur, vm);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 只触发一次的事件</span></span><br><span class="line">        <span class="keyword">if</span> (isTrue(event.once)) &#123;</span><br><span class="line">          cur = on[name] = createOnceHandler(event.name, cur, event.capture);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 执行真正注册事件的执行函数</span></span><br><span class="line">        add(event.name, cur, event.capture, event.passive, event.params);</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (cur !== old) &#123;</span><br><span class="line">        old.fns = cur;</span><br><span class="line">        on[name] = old;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 旧节点存在，接触旧节点上的绑定事件</span></span><br><span class="line">    <span class="keyword">for</span> (name <span class="keyword">in</span> oldOn) &#123;</span><br><span class="line">      <span class="keyword">if</span> (isUndef(on[name])) &#123;</span><br><span class="line">        event = normalizeEvent(name);</span><br><span class="line">        remove###1(event.name, oldOn[name], event.capture);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>在初始构建实例时，旧节点是不存在的,此时会调用<code>createFnInvoker</code>函数对事件回调函数做一层封装，由于单个事件的回调可以有多个，因此<code>createFnInvoker</code>的作用是对单个，多个回调事件统一封装处理，返回一个当事件触发时真正执行的匿名函数。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createFnInvoker</span> (<span class="params">fns, vm</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 当事件触发时，执行invoker方法，方法执行fns</span></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">invoker</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> arguments$<span class="number">1</span> = <span class="built_in">arguments</span>;</span><br><span class="line">    <span class="keyword">var</span> fns = invoker.fns;</span><br><span class="line">    <span class="comment">// fns是多个回调函数组成的数组</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(fns)) &#123;</span><br><span class="line">      <span class="keyword">var</span> cloned = fns.slice();</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; cloned.length; i++) &#123;</span><br><span class="line">        <span class="comment">// 遍历执行真正的回调函数</span></span><br><span class="line">        invokeWithErrorHandling(cloned[i], <span class="literal">null</span>, arguments$<span class="number">1</span>, vm, <span class="string">&quot;v-on handler&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// return handler return value for single handlers</span></span><br><span class="line">      <span class="keyword">return</span> invokeWithErrorHandling(fns, <span class="literal">null</span>, <span class="built_in">arguments</span>, vm, <span class="string">&quot;v-on handler&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  invoker.fns = fns;</span><br><span class="line">  <span class="comment">// 返回最终事件执行的回调函数</span></span><br><span class="line">  <span class="keyword">return</span> invoker</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中<code>invokeWithErrorHandling</code>会执行定义好的回调函数，这里做了同步异步回调的错误处理。<code>try-catch</code>用于同步回调捕获异常错误，<code>Promise.catch</code>用于捕获异步任务返回错误。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">invokeWithErrorHandling</span> (<span class="params">handler,context,args,vm,info</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> res;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      res = args ? handler.apply(context, args) : handler.call(context);</span><br><span class="line">      <span class="keyword">if</span> (res &amp;&amp; !res._isVue &amp;&amp; isPromise(res)) &#123;</span><br><span class="line">        <span class="comment">// issue #9511</span></span><br><span class="line">        <span class="comment">// reassign to res to avoid catch triggering multiple times when nested calls</span></span><br><span class="line">        <span class="comment">// 当生命周期钩子函数内部执行返回promise对象是，如果捕获异常，则会对异常信息做一层包装返回</span></span><br><span class="line">        res = res.catch(<span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>&#123; <span class="keyword">return</span> handleError(e, vm, info + <span class="string">&quot; (Promise/async)&quot;</span>); &#125;);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      handleError(e, vm, info);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>如果事件只触发一次(即使用了<code>once</code>修饰符)，则调用<code>createOnceHandler</code>匿名，在执行完回调之后，移除事件绑定。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createOnceHandler</span> (<span class="params">event, handler, capture</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> _target = target$<span class="number">1</span>; </span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">onceHandler</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="comment">//调用事件回调</span></span><br><span class="line">      <span class="keyword">var</span> res = handler.apply(<span class="literal">null</span>, <span class="built_in">arguments</span>);</span><br><span class="line">      <span class="keyword">if</span> (res !== <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 移除事件绑定</span></span><br><span class="line">        remove$<span class="number">2</span>(event, onceHandler, capture, _target);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p><strong><code>add</code>和<code>remove</code>是真正在<code>DOM</code>上绑定事件和解绑事件的过程，它的实现也是利用了原生<code>DOM</code>的<code>addEventListener,removeEventListener api</code>。</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span> (<span class="params">name,handler,capture,passive</span>)</span>&#123;</span><br><span class="line">  ···</span><br><span class="line">  target$<span class="number">1.</span>addEventListener(name,handler,</span><br><span class="line">      supportsPassive</span><br><span class="line">        ? &#123; <span class="attr">capture</span>: capture, <span class="attr">passive</span>: passive &#125;</span><br><span class="line">        : capture);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">remove</span> (<span class="params">name,handler,capture,_target</span>) </span>&#123;</span><br><span class="line">  (_target || target$<span class="number">1</span>).removeEventListener(</span><br><span class="line">    name,</span><br><span class="line">    handler._wrapper || handler,</span><br><span class="line">    capture</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>另外事件的解绑除了发生在只触发一次的事件，也发生在组件更新<code>patchVnode</code>过程</p>
<h2 id="自定义事件"><a href="#自定义事件" class="headerlink" title="自定义事件"></a>自定义事件</h2><p>父子组件可以利用事件进行通信，子组件通过<code>vm.$emit</code>向父组件分发事件，父组件通过<code>v-on:(event)</code>接收信息并处理回调。因此针对自定义事件在源码中自然有不同的处理逻辑。我们先通过简单的例子展开。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> child = &#123;</span><br><span class="line">    template: <span class="string">`&lt;div @click=&quot;emitToParent&quot;&gt;点击传递信息给父组件&lt;/div&gt;`</span>,</span><br><span class="line">    methods: &#123;</span><br><span class="line">        <span class="function"><span class="title">emitToParent</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.$emit(<span class="string">&#x27;myevent&#x27;</span>, <span class="number">1</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">new</span> Vue(&#123;</span><br><span class="line">    el: <span class="string">&#x27;#app&#x27;</span>,</span><br><span class="line">    components: &#123;</span><br><span class="line">        child</span><br><span class="line">    &#125;,</span><br><span class="line">    template: <span class="string">`&lt;div id=&quot;app&quot;&gt;&lt;child @myevent=&quot;myevent&quot; @click.native=&quot;nativeClick&quot;&gt;&lt;/child&gt;&lt;/div&gt;`</span>,</span><br><span class="line">    methods: &#123;</span><br><span class="line">        <span class="function"><span class="title">myevent</span>(<span class="params">num</span>)</span> &#123;</span><br><span class="line">            <span class="built_in">console</span>.log(num)</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="function"><span class="title">nativeClick</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">&#x27;nativeClick&#x27;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>从例子中可以看出，普通节点只能使用原生<code>DOM</code>事件，而组件上却可以使用自定义的事件和原生的<code>DOM</code>事件，并且通过<code>native</code>修饰符区分，有了原生<code>DOM</code>对于事件处理的基础，接下来我们看看自定义事件有什么特别之处。</p>
<h3 id="模板编译-1"><a href="#模板编译-1" class="headerlink" title="模板编译"></a>模板编译</h3><p>在生成<code>AST</code>树阶段，之前分析说过<code>addHandler</code>方法会对事件的修饰符做不同的处理，当遇到<code>native</code>修饰符时，事件相关属性方法会添加到<code>nativeEvents</code>属性中。下图是<code>child</code>生成的<code>AST</code>树:</p>
<p><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/9.3.png" alt="9.4. 自定义事件 - 图1"></p>
<h3 id="代码生成-1"><a href="#代码生成-1" class="headerlink" title="代码生成"></a>代码生成</h3><p>不管是组件还是普通标签，事件处理代码都在<code>genData</code>的过程中，和之前分析原生事件一致，<code>genHandlers</code>用来处理事件对象并拼接成字符串。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">genData</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  ···</span><br><span class="line">  <span class="keyword">if</span> (el.events) &#123;</span><br><span class="line">    data += (genHandlers(el.events, <span class="literal">false</span>)) + <span class="string">&quot;,&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (el.nativeEvents) &#123;</span><br><span class="line">    data += (genHandlers(el.nativeEvents, <span class="literal">true</span>)) + <span class="string">&quot;,&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>getHandlers</code>的逻辑前面已经讲过，处理组件原生事件和自定义事件的区别在<code>isNative</code>选项上，我们看最终生成的代码为：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">with</span>(<span class="params"><span class="built_in">this</span></span>)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> _c(<span class="string">&#x27;div&#x27;</span>, &#123;</span><br><span class="line">        attrs: &#123;</span><br><span class="line">            <span class="string">&quot;id&quot;</span>: <span class="string">&quot;app&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, [_c(<span class="string">&#x27;child&#x27;</span>, &#123;</span><br><span class="line">        on: &#123;</span><br><span class="line">            <span class="string">&quot;myevent&quot;</span>: myevent</span><br><span class="line">        &#125;,</span><br><span class="line">        nativeOn: &#123;</span><br><span class="line">            <span class="string">&quot;click&quot;</span>: <span class="function"><span class="keyword">function</span>(<span class="params">$event</span>) </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> nativeClick($event)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)], <span class="number">1</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>有了<code>render</code>函数接下来会根据它创建<code>Vnode</code>实例，其中遇到组件占位符节点时会创建子组件<code>Vnode</code>， 此时为<code>on,nativeOn</code>做了一层特殊的转换，将<code>nativeOn</code>赋值给<code>on</code>,这样后续的处理方式和普通节点一致。另外，将<code>on</code>赋值给<code>listeners</code>,在创建<code>VNode</code>时以组件配置<code>componentOptions</code>传入。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">// 创建子组件过程</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createComponent</span> (<span class="params"></span>)</span>&#123;</span><br><span class="line">  ···</span><br><span class="line">  <span class="keyword">var</span> listeners = data.on;</span><br><span class="line">  <span class="comment">// replace with listeners with .native modifier</span></span><br><span class="line">  <span class="comment">// so it gets processed during parent component patch.</span></span><br><span class="line">  data.on = data.nativeOn;</span><br><span class="line">  ···</span><br><span class="line">  <span class="keyword">var</span> vnode = <span class="keyword">new</span> VNode(</span><br><span class="line">    (<span class="string">&quot;vue-component-&quot;</span> + (Ctor.cid) + (name ? (<span class="string">&quot;-&quot;</span> + name) : <span class="string">&#x27;&#x27;</span>)),</span><br><span class="line">    data, <span class="literal">undefined</span>, <span class="literal">undefined</span>, <span class="literal">undefined</span>, context,</span><br><span class="line">    &#123; <span class="attr">Ctor</span>: Ctor, <span class="attr">propsData</span>: propsData, <span class="attr">listeners</span>: listeners, <span class="attr">tag</span>: tag, <span class="attr">children</span>: children &#125;,</span><br><span class="line">    asyncFactory</span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">return</span> vnode</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="子组件实例"><a href="#子组件实例" class="headerlink" title="子组件实例"></a>子组件实例</h3><p>接下来是通过<code>Vnode</code>生成真实节点的过程，这个过程遇到子<code>Vnode</code>会实例化子组件实例。实例化子类构造器的过程又回到初始化选项配置，执行Vue.prototype.init`的过程，跳过了组件初始化的流程，其中针对自定义事件的处理的关键如下</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">Vue.prototype._init = <span class="function"><span class="keyword">function</span>(<span class="params">options</span>) </span>&#123;</span><br><span class="line">  ···</span><br><span class="line">  <span class="comment">// 针对子组件的事件处理逻辑</span></span><br><span class="line">  <span class="keyword">if</span> (options &amp;&amp; options._isComponent) &#123;</span><br><span class="line">    <span class="comment">// 初始化内部组件</span></span><br><span class="line">    initInternalComponent(vm, options);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 选项合并，将合并后的选项赋值给实例的$options属性</span></span><br><span class="line">    vm.$options = mergeOptions(</span><br><span class="line">      resolveConstructorOptions(vm.constructor),</span><br><span class="line">      options || &#123;&#125;,</span><br><span class="line">      vm</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 初始化事件处理</span></span><br><span class="line">  initEvents(vm);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">initInternalComponent</span> (<span class="params">vm, options</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> opts = vm.$options = <span class="built_in">Object</span>.create(vm.constructor.options);</span><br><span class="line">  ···</span><br><span class="line">  opts._parentListeners = vnodeComponentOptions.listeners;</span><br><span class="line">  ···</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>此时，子组件拿到了父占位符节点定义的<code>@myevent=&quot;myevent&quot;</code>事件</strong>。接下来进行子组件的初始化事件处理，此时<code>vm.$options._parentListeners</code>会拿到父组件自定义的事件。而带有自定义事件的组件会执行<code>updateComponentListeners</code>函数。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">initEvents</span> (<span class="params">vm</span>) </span>&#123;</span><br><span class="line">  vm._events = <span class="built_in">Object</span>.create(<span class="literal">null</span>);</span><br><span class="line">  vm._hasHookEvent = <span class="literal">false</span>;</span><br><span class="line">  <span class="comment">// init parent attached events</span></span><br><span class="line">  <span class="keyword">var</span> listeners = vm.$options._parentListeners;</span><br><span class="line">  <span class="keyword">if</span> (listeners) &#123;</span><br><span class="line">    <span class="comment">// 带有自定义事件属性的实例</span></span><br><span class="line">    updateComponentListeners(vm, listeners);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>之后又回到了之前分析的<code>updateListeners</code>过程，和原生<code>DOM</code>事件不同的是，自定义事件的添加移除的方法不同。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> target = vm;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span> (<span class="params">event, fn</span>) </span>&#123;</span><br><span class="line">  target.$on(event, fn);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">remove$1</span> (<span class="params">event, fn</span>) </span>&#123;</span><br><span class="line">  target.$off(event, fn);</span><br><span class="line">&#125; </span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">updateComponentListeners</span> (<span class="params">vm,listeners,oldListeners</span>) </span>&#123;</span><br><span class="line">  target = vm;</span><br><span class="line">  updateListeners(listeners, oldListeners || &#123;&#125;, add, remove$<span class="number">1</span>, createOnceHandler, vm);</span><br><span class="line">  target = <span class="literal">undefined</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="事件API"><a href="#事件API" class="headerlink" title="事件API"></a>事件API</h3><p><code>Vue</code>在引入阶段对事件的处理还做了哪些初始化操作。<code>Vue</code>在实例上用一个<code>_events</code>属性存贮管理事件的派发和更新，暴露出<code>$on, $once, $off, $emit</code>方法给外部管理事件和派发执行事件。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line">eventsMixin(Vue); <span class="comment">// 定义事件相关函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">eventsMixin</span> (<span class="params">Vue</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> hookRE = <span class="regexp">/^hook:/</span>;</span><br><span class="line">  <span class="comment">// $on方法用来监听事件，执行回调</span></span><br><span class="line">  Vue.prototype.$on = <span class="function"><span class="keyword">function</span> (<span class="params">event, fn</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> vm = <span class="built_in">this</span>;</span><br><span class="line">    <span class="comment">// event支持数组形式。</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(event)) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, l = event.length; i &lt; l; i++) &#123;</span><br><span class="line">        vm.$on(event[i], fn);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// _events数组中记录需要监听的事件以及事件触发的回调</span></span><br><span class="line">      (vm._events[event] || (vm._events[event] = [])).push(fn);</span><br><span class="line">      <span class="keyword">if</span> (hookRE.test(event)) &#123;</span><br><span class="line">        vm._hasHookEvent = <span class="literal">true</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> vm</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="comment">// $once方法用来监听一次事件，执行回调</span></span><br><span class="line">  Vue.prototype.$once = <span class="function"><span class="keyword">function</span> (<span class="params">event, fn</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> vm = <span class="built_in">this</span>;</span><br><span class="line">    <span class="comment">// 对fn做一层包装，先解除绑定再执行fn回调</span></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">on</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">      vm.$off(event, on);</span><br><span class="line">      fn.apply(vm, <span class="built_in">arguments</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    on.fn = fn;</span><br><span class="line">    vm.$on(event, on);</span><br><span class="line">    <span class="keyword">return</span> vm</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="comment">// $off方法用来解除事件监听</span></span><br><span class="line">  Vue.prototype.$off = <span class="function"><span class="keyword">function</span> (<span class="params">event, fn</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> vm = <span class="built_in">this</span>;</span><br><span class="line">    <span class="comment">// 如果$off方法没有传递任何参数时，将_events属性清空。</span></span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">arguments</span>.length) &#123;</span><br><span class="line">      vm._events = <span class="built_in">Object</span>.create(<span class="literal">null</span>);</span><br><span class="line">      <span class="keyword">return</span> vm</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 数组处理</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(event)) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">var</span> i$<span class="number">1</span> = <span class="number">0</span>, l = event.length; i$<span class="number">1</span> &lt; l; i$<span class="number">1</span>++) &#123;</span><br><span class="line">        vm.$off(event[i$<span class="number">1</span>], fn);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> vm</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">var</span> cbs = vm._events[event];</span><br><span class="line">    <span class="keyword">if</span> (!cbs) &#123;</span><br><span class="line">      <span class="keyword">return</span> vm</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (!fn) &#123;</span><br><span class="line">      vm._events[event] = <span class="literal">null</span>;</span><br><span class="line">      <span class="keyword">return</span> vm</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// specific handler</span></span><br><span class="line">    <span class="keyword">var</span> cb;</span><br><span class="line">    <span class="keyword">var</span> i = cbs.length;</span><br><span class="line">    <span class="keyword">while</span> (i--) &#123;</span><br><span class="line">      cb = cbs[i];</span><br><span class="line">      <span class="keyword">if</span> (cb === fn || cb.fn === fn) &#123;</span><br><span class="line">        <span class="comment">// 将监听的事件回调移除</span></span><br><span class="line">        cbs.splice(i, <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> vm</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="comment">// $emit方法用来触发事件，执行回调</span></span><br><span class="line">  Vue.prototype.$emit = <span class="function"><span class="keyword">function</span> (<span class="params">event</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> vm = <span class="built_in">this</span>;</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="keyword">var</span> lowerCaseEvent = event.toLowerCase();</span><br><span class="line">      <span class="keyword">if</span> (lowerCaseEvent !== event &amp;&amp; vm._events[lowerCaseEvent]) &#123;</span><br><span class="line">        tip(</span><br><span class="line">          <span class="string">&quot;Event \&quot;&quot;</span> + lowerCaseEvent + <span class="string">&quot;\&quot; is emitted in component &quot;</span> +</span><br><span class="line">          (formatComponentName(vm)) + <span class="string">&quot; but the handler is registered for \&quot;&quot;</span> + event + <span class="string">&quot;\&quot;. &quot;</span> +</span><br><span class="line">          <span class="string">&quot;Note that HTML attributes are case-insensitive and you cannot use &quot;</span> +</span><br><span class="line">          <span class="string">&quot;v-on to listen to camelCase events when using in-DOM templates. &quot;</span> +</span><br><span class="line">          <span class="string">&quot;You should probably use \&quot;&quot;</span> + (hyphenate(event)) + <span class="string">&quot;\&quot; instead of \&quot;&quot;</span> + event + <span class="string">&quot;\&quot;.&quot;</span></span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">var</span> cbs = vm._events[event];</span><br><span class="line">    <span class="comment">// 找到已经监听事件的回调，执行</span></span><br><span class="line">    <span class="keyword">if</span> (cbs) &#123;</span><br><span class="line">      cbs = cbs.length &gt; <span class="number">1</span> ? toArray(cbs) : cbs;</span><br><span class="line">      <span class="keyword">var</span> args = toArray(<span class="built_in">arguments</span>, <span class="number">1</span>);</span><br><span class="line">      <span class="keyword">var</span> info = <span class="string">&quot;event handler for \&quot;&quot;</span> + event + <span class="string">&quot;\&quot;&quot;</span>;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, l = cbs.length; i &lt; l; i++) &#123;</span><br><span class="line">        invokeWithErrorHandling(cbs[i], vm, args, vm, info);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> vm</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>有了这些事件api，自定义事件的添加移除理解起来也简单很多。组件通过<code>this.$emit</code>在组件实例中派发了事件，而在这之前，组件已经将需要监听的事件以及回调添加到实例的<code>_events</code>属性中，触发事件时便可以直接执行监听事件的回调。</p>
<p><strong>最后，我们换一个角度理解父子组件通信，组件自定义事件的触发和监听本质上都是在当前的组件实例中进行，之所以能产生父子组件通信的效果是因为事件监听的回调函数写在了父组件中。</strong></p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">志非</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://fif.gitee.io/blog/2021/05/14/vue/vue-code-9.event/">https://fif.gitee.io/blog/2021/05/14/vue/vue-code-9.event/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://fif.gitee.io/blog" target="_blank">Jeffery Blog</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/blog/tags/vue/">vue</a><a class="post-meta__tags" href="/blog/tags/vue-%E6%BA%90%E7%A0%81/">vue 源码</a></div><div class="post_share"><div class="social-share" data-image="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/social-share.js/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/blog/2021/05/24/tools/hexo/advanced/"><img class="prev-cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/Hexo-logo.png" onerror="onerror=null;src='/blog/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Hexo 博客页面性能优化</div></div></a></div><div class="next-post pull-right"><a href="/blog/2021/05/04/vue/vue-code-8.component/"><img class="next-cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" onerror="onerror=null;src='/blog/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">（Vue源码六）Vue 组件原理分析</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span> 相关推荐</span></div><div class="relatedPosts-list"><div><a href="/blog/2021/04/12/vue/vue-code-3.build/" title="（Vue源码一）Vue 源码设计和构建流程"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/Vue.js-i18n-768x512.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-12</div><div class="title">（Vue源码一）Vue 源码设计和构建流程</div></div></a></div><div><a href="/blog/2021/04/17/vue/vue-code-4.debugger/" title="（Vue源码二）Vue实例挂载流程"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/1_b2Ig4HR2UOQEV5I2QnQrrw.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-17</div><div class="title">（Vue源码二）Vue实例挂载流程</div></div></a></div><div><a href="/blog/2021/04/23/vue/vue-code-5.mergeOptions/" title="（Vue源码三）Vue 丰富的选项合并策略"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vue-highcharts_1200.webp" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-23</div><div class="title">（Vue源码三）Vue 丰富的选项合并策略</div></div></a></div><div><a href="/blog/2021/04/30/vue/vue-code-7-compiler/" title="（Vue源码五）Vue 编译原理"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-30</div><div class="title">（Vue源码五）Vue 编译原理</div></div></a></div><div><a href="/blog/2021/05/04/vue/vue-code-8.component/" title="（Vue源码六）Vue 组件原理分析"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-05-04</div><div class="title">（Vue源码六）Vue 组件原理分析</div></div></a></div><div><a href="/blog/2021/04/27/vue/vue-code-6.reactive/" title="（Vue源码四）数据响应式原理"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/mvvm.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-27</div><div class="title">（Vue源码四）数据响应式原理</div></div></a></div></div></div><hr/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div class="vcomment" id="vcomment"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="card-info-avatar is-center"><img class="avatar-img" src= "" data-lazy-src="/blog/img/home/avatar.jpeg" onerror="this.onerror=null;this.src='/blog/img/friend_404.gif'" alt="avatar"/><div class="author-info__name">志非</div><div class="author-info__description">自己变得更好，生活才会更美好</div></div><div class="card-info-data"><div class="card-info-data-item is-center"><a href="/blog/archives/"><div class="headline">文章</div><div class="length-num">12</div></a></div><div class="card-info-data-item is-center"><a href="/blog/tags/"><div class="headline">标签</div><div class="length-num">6</div></a></div><div class="card-info-data-item is-center"><a href="/blog/categories/"><div class="headline">分类</div><div class="length-num">7</div></a></div></div><a class="button--animated" id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/JefferyXZF"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://github.com/JefferyXZF" target="_blank" title="Github"><i class="fab fa-github"></i></a><a class="social-icon" href="https://www.yuque.com/jeffery-kkrpn/uz24n9" target="_blank" title="语雀"><i class="fa fa-folder-open"></i></a><a class="social-icon" href="https://mail.qq.com/" target="_blank" title="Email"><i class="fas fa-envelope"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn card-announcement-animation"></i><span>公告</span></div><div class="announcement_content">越努力越幸运</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A6%82%E8%BF%B0"><span class="toc-number">1.</span> <span class="toc-text">概述</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A8%A1%E6%9D%BF%E7%BC%96%E8%AF%91"><span class="toc-number">2.</span> <span class="toc-text">模板编译</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90"><span class="toc-number">3.</span> <span class="toc-text">代码生成</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BA%8B%E4%BB%B6%E7%BB%91%E5%AE%9A"><span class="toc-number">4.</span> <span class="toc-text">事件绑定</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6"><span class="toc-number">5.</span> <span class="toc-text">自定义事件</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A8%A1%E6%9D%BF%E7%BC%96%E8%AF%91-1"><span class="toc-number">5.1.</span> <span class="toc-text">模板编译</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90-1"><span class="toc-number">5.2.</span> <span class="toc-text">代码生成</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AD%90%E7%BB%84%E4%BB%B6%E5%AE%9E%E4%BE%8B"><span class="toc-number">5.3.</span> <span class="toc-text">子组件实例</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BA%8B%E4%BB%B6API"><span class="toc-number">5.4.</span> <span class="toc-text">事件API</span></a></li></ol></li></ol></div></div><script defer="defer" data-pjax="data-pjax" src="/blog/js/card_clock.js"></script><div class="card-widget card-clock"><div id="clock"><img v-if="clockshow == &quot;false&quot;" alt="" src= "" data-lazy-src="/blog/img/weather/loading.gif" height="120px" width="100%"/><table class="clock" v-if="clockshow"><tbody><tr><td class="clockdate">{{date}}</td><td class="weatherimg"><img id="weatherimg" :src="weatherimg" alt=""/></td><td class="temperature">{{temperature}}</td><td class="humidityimg"><img id="humidityimg" :src="humidityimg" alt=""/></td><td class="humidity">{{humidity}}</td></tr><tr class="time"><td colspan="5">{{time}}</td></tr><tr><td class="usaqi" colspan="1"><span>{{ip}}</span></td><td class="city" colspan="2">{{city}}</td><td class="daylight" colspan="2">{{daylight}}</td></tr></tbody></table></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/05/24/tools/hexo/advanced/" title="Hexo 博客页面性能优化"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/Hexo-logo.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="Hexo 博客页面性能优化"/></a><div class="content"><a class="title" href="/blog/2021/05/24/tools/hexo/advanced/" title="Hexo 博客页面性能优化">Hexo 博客页面性能优化</a><time datetime="2021-05-23T16:00:00.000Z" title="发表于 2021-05-24 00:00:00">2021-05-24</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/05/14/vue/vue-code-9.event/" title="（Vue源码七）Vue 事件"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码七）Vue 事件"/></a><div class="content"><a class="title" href="/blog/2021/05/14/vue/vue-code-9.event/" title="（Vue源码七）Vue 事件">（Vue源码七）Vue 事件</a><time datetime="2021-05-13T16:00:00.000Z" title="发表于 2021-05-14 00:00:00">2021-05-14</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/05/04/vue/vue-code-8.component/" title="（Vue源码六）Vue 组件原理分析"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码六）Vue 组件原理分析"/></a><div class="content"><a class="title" href="/blog/2021/05/04/vue/vue-code-8.component/" title="（Vue源码六）Vue 组件原理分析">（Vue源码六）Vue 组件原理分析</a><time datetime="2021-05-03T16:00:00.000Z" title="发表于 2021-05-04 00:00:00">2021-05-04</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/04/30/vue/vue-code-7-compiler/" title="（Vue源码五）Vue 编译原理"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码五）Vue 编译原理"/></a><div class="content"><a class="title" href="/blog/2021/04/30/vue/vue-code-7-compiler/" title="（Vue源码五）Vue 编译原理">（Vue源码五）Vue 编译原理</a><time datetime="2021-04-29T16:00:00.000Z" title="发表于 2021-04-30 00:00:00">2021-04-30</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/04/27/vue/vue-code-6.reactive/" title="（Vue源码四）数据响应式原理"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/mvvm.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码四）数据响应式原理"/></a><div class="content"><a class="title" href="/blog/2021/04/27/vue/vue-code-6.reactive/" title="（Vue源码四）数据响应式原理">（Vue源码四）数据响应式原理</a><time datetime="2021-04-26T16:00:00.000Z" title="发表于 2021-04-27 00:00:00">2021-04-27</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2021 By 志非</div><div class="footer_custom_text">Hi, welcome to my <a href="/">blog</a>!</div><div id="workboard"></div><script async="async" src="/blog/js/runtime.js"></script></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="chat_btn" type="button" title="rightside.chat_btn"><i class="fas fa-sms"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comments"></i></a><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="algolia-search"><div class="search-dialog"><div class="search-dialog__title" id="algolia-search-title">Algolia</div><div id="algolia-input-panel"><div id="algolia-search-input"></div></div><hr/><div id="algolia-search-results"><div id="algolia-hits"></div><div id="algolia-pagination"></div><div id="algolia-stats"></div></div><span class="search-close-button"><i class="fas fa-times"></i></span></div><div id="search-mask"></div></div><div><script src="https://cdn.jsdelivr.net/npm/vue"></script><script src="/blog/js/utils.js"></script><script src="/blog/js/main.js"></script><script src="/blog/js/tw_cn.js"></script><script src="https://cdn.jsdelivr.net/npm/medium-zoom/dist/medium-zoom.min.js"></script><script src="https://cdn.jsdelivr.net/npm/instant.page/instantpage.min.js" type="module"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script src="/blog/js/search/algolia.js"></script><script>var preloader = {
  endLoading: () => {
    document.body.style.overflow = 'auto';
    document.getElementById('loading-box').classList.add("loaded")
  },
  initLoading: () => {
    document.body.style.overflow = '';
    document.getElementById('loading-box').classList.remove("loaded")

  }
}
window.addEventListener('load',preloader.endLoading())</script><div class="js-pjax"><script>if (document.getElementsByClassName('mermaid').length) {
  if (window.mermaidJsLoad) mermaid.init()
  else {
    getScript('https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js').then(() => {
      window.mermaidJsLoad = true
      mermaid.initialize({
        theme: 'default',
      })
      false && mermaid.init()
    })
  }
}</script><script>function loadValine () {
  function initValine () {
    const valine = new Valine(Object.assign({
      el: '#vcomment',
      appId: 'JC72d1A7XBwC26rGCKMVn5CS-MdYXbMMI',
      appKey: 'xI6bG7FseICz8ShKdmWVe7N1',
      placeholder: 'Please leave your footprints',
      avatar: 'https://gitee.com/FIF/pic-beg/raw/master/images/vue/1619248532079-avatar_coment.webp',
      meta: 'nick,mail,link'.split(','),
      pageSize: '10',
      lang: 'zh-CN',
      recordIP: false,
      serverURLs: '',
      emojiCDN: '',
      emojiMaps: "",
      enableQQ: true,
      path: window.location.pathname,
      requiredFields: ["nick,mail"],
      visitor: false,
      avatar_cdn: 'https://www.gravatar.com/avatar/'
    }, null))
  }

  if (typeof Valine === 'function') initValine() 
  else getScript('https://cdn.jsdelivr.net/npm/valine/dist/Valine.min.js').then(initValine)
}

if ('Valine' === 'Valine' || !true) {
  if (true) btf.loadComment(document.getElementById('vcomment'),loadValine)
  else setTimeout(loadValine, 0)
} else {
  function loadOtherComment () {
    loadValine()
  }
}</script></div><div class="aplayer no-destroy" data-id="000PeZCQ1i4XVs" data-server="tencent" data-type="artist" data-fixed="true" data-mini="true" data-listFolded="false" data-order="random" data-preload="none" data-autoplay="false" pjax="true" muted></div><script defer="defer" id="ribbon" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-ribbon.min.js" size="150" alpha="0.6" zIndex="-1" mobile="false" data-click="true"></script><script id="canvas_nest" defer="defer" color="0,0,255" opacity="0.7" zIndex="-1" count="99" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-nest.min.js"></script><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/activate-power-mode.min.js"></script><script>POWERMODE.colorful = true;
POWERMODE.shake = true;
POWERMODE.mobile = false;
document.body.addEventListener('input', POWERMODE);
</script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script><script src="https://cdn.jsdelivr.net/gh/metowolf/MetingJS@1.2/dist/Meting.min.js"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>